import ctypes
import logging
import math
import platform
import sys
import time
import traceback

import numpy as np
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtGui import QIcon
from PyQt5.QtNetwork import QLocalSocket, QLocalServer
from PyQt5.QtWidgets import QMainWindow, QFileDialog, QStatusBar

import errorhandle
from aboutdialog import AboutDialog
from bscangraph_pg import BScanGraphPG
from configs.strings import combox, translate
from connexions.rebarconnector import RebarConnector
from connexions.udpserver import UDPServer
from tools import tools
from tools.pulsecomp import invczt, remove_back
from tools.tools import basic_log_config, load_yaml, save_data_pickle
from tools.workthread import WorkThread
from vnaconnector import R60
from voltagegraph import VoltageGraph
from wavegraph import WaveGraph

"""TODO
1. 加入连续测模式
2. 加入机制提示用户滑轮过快
3. 长时间没有测量仪回应会触发一个list out of range错误
"""


class MainFrame(QMainWindow):
    def __init__(self):
        super(MainFrame, self).__init__()
        basic_log_config()
        self.config = load_yaml()
        self.lang = combox[self.config["APP"]["LANGUAGE"]]
        self.init_args()
        self.init_ui()
        self.init_draw_panel()
        self.init_bottom()
        self.init_widget_status()
        self.init_slots()

    def init_ui(self):
        """
        初始化高亮区界面，菜单栏，按钮，设置等界面
        :return:
        """
        logging.info("INIT UI....")
        sys = platform.system()
        if sys == "Windows":
            ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(
                self.config["APP"]["APP_NAME"])

        self.centralwidget = QtWidgets.QWidget()
        self.centralwidget.setEnabled(True)
        self.setCentralWidget(self.centralwidget)

        self.mainLayout = QtWidgets.QGridLayout(self.centralwidget)
        self.mainWidget = QtWidgets.QWidget()
        self.setGeometry(QtCore.QRect(0, 0, 1100, 1000))
        self.setWindowTitle(self.config["APP"]["APP_NAME"])
        self.setWindowIcon(QIcon(self.config["APP"]["ICON"]))
        self.mainWidget.setLayout(self.mainLayout)
        self.setCentralWidget(self.mainWidget)

        # Menu UI
        logging.info("Init Menu...")
        self.menubar = self.menuBar()
        self.menuConfiguration = self.menubar.addMenu(translate['FILE'][self.lang])
        self.saveConfigAction = QtWidgets.QAction(translate['SAVE_CONFIG'][self.lang])
        self.loadConfigAction = QtWidgets.QAction(translate['LOAD_CONFIG'][self.lang])
        self.saveCurrentDataAction = QtWidgets.QAction(translate['SAVE_DATA'][self.lang])
        self.menuConfiguration.addAction(self.saveConfigAction)
        self.menuConfiguration.addAction(self.loadConfigAction)
        self.menuConfiguration.addAction(self.saveCurrentDataAction)
        self.menubar.addAction(self.menuConfiguration.menuAction())

        self.menuFunc = self.menubar.addMenu(translate['FUNCTION'][self.lang])
        self.reconRebarAction = QtWidgets.QAction(translate['RECONNECT_REBAR'][self.lang])
        self.reconVNAAction = QtWidgets.QAction(translate['RECONNECT_VNA'][self.lang])
        self.updateCaliFile = QtWidgets.QAction(translate['UPDATE_CALI_FILE'][self.lang])
        self.menuFunc.addAction(self.reconRebarAction)
        self.menuFunc.addAction(self.reconVNAAction)
        self.menuFunc.addAction(self.updateCaliFile)
        self.menubar.addAction(self.menuFunc.menuAction())

        self.menuHelp = self.menubar.addMenu(translate['HELP'][self.lang])
        self.aboutAction = QtWidgets.QAction(translate['ABOUT'][self.lang])
        self.menuHelp.addAction(self.aboutAction)
        self.menubar.addAction(self.menuHelp.menuAction())

        self.statusBar = QStatusBar()
        self.setStatusBar(self.statusBar)

        # Tool Button
        logging.info("Init Tool bar...")
        self.btnToolbar = QtWidgets.QHBoxLayout(self.centralwidget)
        configGroupBox = QtWidgets.QGroupBox("")
        configGroupBox.setLayout(self.btnToolbar)
        self.mainLayout.addWidget(configGroupBox, 0, 0, 1, 1)

        self.startBtn = QtWidgets.QToolButton()
        self.startBtn.setText(translate["START"][self.lang])
        self.startBtn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
        self.startBtn.setIcon(QtGui.QIcon(self.config["APP"]["START_COL_ICON"]))
        self.startBtn.setIconSize(QtCore.QSize(50, 50))

        self.diameterMeasBtn = QtWidgets.QToolButton()
        self.diameterMeasBtn.setText(translate["DIAMETER_MEAS"][self.lang])
        self.diameterMeasBtn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
        self.diameterMeasBtn.setIcon(QtGui.QIcon(self.config["APP"]["TOOLS_ICON"]))
        self.diameterMeasBtn.setIconSize(QtCore.QSize(50, 50))

        self.diameterGuessBtn = QtWidgets.QToolButton()
        self.diameterGuessBtn.setText(translate["DIAMETER_GUESS"][self.lang])
        self.diameterGuessBtn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
        self.diameterGuessBtn.setIcon(QtGui.QIcon(self.config["APP"]["TOOLS_ICON"]))
        self.diameterGuessBtn.setIconSize(QtCore.QSize(50, 50))

        self.stopBtn = QtWidgets.QToolButton()
        self.stopBtn.setText(translate["STOP"][self.lang])
        self.stopBtn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
        self.stopBtn.setIcon(QtGui.QIcon(self.config["APP"]["STOP_COL_ICON"]))
        self.stopBtn.setIconSize(QtCore.QSize(50, 50))

        self.setParaBtn = QtWidgets.QToolButton()
        self.setParaBtn.setText(translate["SET_PARA"][self.lang])
        self.setParaBtn.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
        self.setParaBtn.setIcon(QtGui.QIcon(self.config["APP"]["TOOLS_ICON"]))
        self.setParaBtn.setIconSize(QtCore.QSize(50, 50))

        # ========================= Selection List=========================
        self.argsSelectorLayout = QtWidgets.QGridLayout(self.centralwidget)
        selectorGroupBox = QtWidgets.QGroupBox("")
        selectorGroupBox.setLayout(self.argsSelectorLayout)
        self.mainLayout.addWidget(selectorGroupBox, 0, 1, 1, 4)

        self.initFreqLabel = QtWidgets.QLabel(translate["INI_FREQ"][self.lang])
        self.initFreqLabel.setAlignment(QtCore.Qt.AlignRight)
        self.initFreqEdit = QtWidgets.QLineEdit()
        self.initFreqEdit.setText(str(self.f1Hz / 1e6))

        self.endFreqLabel = QtWidgets.QLabel(translate["END_FREQ"][self.lang])
        self.endFreqLabel.setAlignment(QtCore.Qt.AlignRight)
        self.endFreqEdit = QtWidgets.QLineEdit()
        self.endFreqEdit.setText(str(self.f2Hz / 1e6))

        self.freqPtsLabel = QtWidgets.QLabel(translate["FREQ_PTS"][self.lang])
        self.freqPtsLabel.setAlignment(QtCore.Qt.AlignRight)
        self.freqPtsCombo = QtWidgets.QComboBox()
        self.freqPtsCombo.addItem(str(self.config['SERIAL']['FREQ_PTS']))
        self.freqPtsCombo.addItems(combox['FREQ_PTS'])

        self.interFreqBWLabel = QtWidgets.QLabel(translate["INTERMEDIA_FREQ_BW"][self.lang])
        self.interFreqBWLabel.setAlignment(QtCore.Qt.AlignRight)
        self.interFreqBWCombo = QtWidgets.QComboBox()
        self.interFreqBWCombo.addItems(combox['INTERMEDIA_FREQ_BW'])

        self.workModeLabel = QtWidgets.QLabel(translate["WORK_MODE"][self.lang])
        self.workModeLabel.setAlignment(QtCore.Qt.AlignRight)
        self.workModeCombo = QtWidgets.QComboBox()
        workList = [translate[i][self.lang] for i in combox['WORK_MODE']]
        self.workModeCombo.addItems(workList)

        self.rebarRangeLabel = QtWidgets.QLabel(translate["REBAR_RANGE"][self.lang])
        self.rebarRangeLabel.setAlignment(QtCore.Qt.AlignRight)
        self.rebarRangeCombo = QtWidgets.QComboBox()
        tempRangeList = combox['REBAR_RANGE']
        if self.config['BASIC_PARA']['REBAR_RANGE'] in tempRangeList:
            target = tempRangeList.index(self.config['BASIC_PARA']['REBAR_RANGE'])
            if target != 0:
                tempRangeList[0], tempRangeList[target] = self.config['BASIC_PARA']['REBAR_RANGE'], tempRangeList[0]
        self.rebarRangeCombo.addItems([translate[i][self.lang] for i in tempRangeList])

        self.rebarDiameterLabel = QtWidgets.QLabel(translate["REBAR_DIAMETER"][self.lang])
        self.rebarDiameterLabel.setAlignment(QtCore.Qt.AlignRight)
        self.rebarDiameterCombo = QtWidgets.QComboBox()
        tempDiameterList = combox['REBAR_DIAMETER']
        tempDiameter = str(self.config['BASIC_PARA']['REBAR_DIAMETER'])
        if tempDiameter in tempDiameterList:
            target = tempDiameterList.index(tempDiameter)
            if target != 0:
                tempDiameterList[0], tempDiameterList[target] = tempDiameter, tempDiameterList[0]
        self.rebarDiameterCombo.addItems(tempDiameterList)

        self.permittivityLabel = QtWidgets.QLabel(translate["PERMITTIVITY"][self.lang])
        self.permittivityLabel.setAlignment(QtCore.Qt.AlignRight)
        self.permittivityEdit = QtWidgets.QLineEdit()
        self.permittivityEdit.setText(str(self.config['BASIC_PARA']['PERMITTIVITY']))

        self.argsSelectorLayout.addWidget(self.initFreqLabel, 0, 0, 1, 1)
        self.argsSelectorLayout.addWidget(self.initFreqEdit, 0, 1, 1, 2)
        self.argsSelectorLayout.addWidget(self.endFreqLabel, 0, 3, 1, 1)
        self.argsSelectorLayout.addWidget(self.endFreqEdit, 0, 4, 1, 2)
        self.argsSelectorLayout.addWidget(self.freqPtsLabel, 0, 6, 1, 1)
        self.argsSelectorLayout.addWidget(self.freqPtsCombo, 0, 7, 1, 2)
        self.argsSelectorLayout.addWidget(self.interFreqBWLabel, 0, 9, 1, 1)
        self.argsSelectorLayout.addWidget(self.interFreqBWCombo, 0, 10, 1, 2)

        self.argsSelectorLayout.addWidget(self.workModeLabel, 1, 0, 1, 1)
        self.argsSelectorLayout.addWidget(self.workModeCombo, 1, 1, 1, 2)
        self.argsSelectorLayout.addWidget(self.rebarRangeLabel, 1, 3, 1, 1)
        self.argsSelectorLayout.addWidget(self.rebarRangeCombo, 1, 4, 1, 2)
        self.argsSelectorLayout.addWidget(self.rebarDiameterLabel, 1, 6, 1, 1)
        self.argsSelectorLayout.addWidget(self.rebarDiameterCombo, 1, 7, 1, 2)
        self.argsSelectorLayout.addWidget(self.permittivityLabel, 1, 9, 1, 1)
        self.argsSelectorLayout.addWidget(self.permittivityEdit, 1, 10, 1, 2)

        self.btnToolbar.addWidget(self.startBtn)
        self.btnToolbar.addWidget(self.diameterMeasBtn)
        self.btnToolbar.addWidget(self.diameterGuessBtn)
        self.btnToolbar.addWidget(self.stopBtn)
        self.btnToolbar.addWidget(self.setParaBtn)

    def init_draw_panel(self):
        """
        初始化实时数据面显示界面，单次波形图，多次数据融合灰度图，电量图
        由于pygraph的一些问题，导致无法把界面填满，有点烦恼。。
        :return:
        """
        # Central UI
        self.bscanPanel = BScanGraphPG(samplePoint=self.config['SERIAL']['SAMPLE_POINTS'])
        self.mainLayout.addWidget(self.bscanPanel, 1, 1, 4, 10)

        self.wavePanel = WaveGraph()
        self.mainLayout.addWidget(self.wavePanel, 1, 0, 4, 1)

        self.voltagePanel = VoltageGraph()
        self.mainLayout.addWidget(self.voltagePanel, 6, 0, 2, 11)

    def init_bottom(self):
        """
        初始化底部界面，数据显示，加入样式
        :return:
        """
        # Botton UI
        logging.info("Int bottom UI...")
        self.bottomLayout = QtWidgets.QGridLayout()
        infoGroupBox = QtWidgets.QGroupBox("")
        infoGroupBox.setLayout(self.bottomLayout)

        self.counterLabel = QtWidgets.QLabel(tools.make_font(msg=translate['COUNTER'][self.lang] + ':'))
        self.counterLabel.setAlignment(QtCore.Qt.AlignRight)
        self.counterStr = QtWidgets.QLabel(tools.make_font(msg=str(self.count)))
        self.counterStr.setAlignment(QtCore.Qt.AlignLeft)

        self.rangeChosenLabel = QtWidgets.QLabel(tools.make_font(msg=translate['RANGE_CHOOSE'][self.lang] + ':'))
        self.rangeChosenLabel.setAlignment(QtCore.Qt.AlignRight)
        self.rangeChosenStr = QtWidgets.QLabel(tools.make_font(msg=self.rebarRangeCombo.currentText()))
        self.rangeChosenStr.setAlignment(QtCore.Qt.AlignLeft)

        self.diameterLabel = QtWidgets.QLabel(tools.make_font(msg=translate['REBAR_DIAMETER'][self.lang] + ':'))
        self.diameterLabel.setAlignment(QtCore.Qt.AlignRight)
        self.diameterStr = QtWidgets.QLabel(tools.make_font(msg=self.rebarDiameterCombo.currentText()))
        self.diameterStr.setAlignment(QtCore.Qt.AlignLeft)

        self.rebarRangeBotLabel = QtWidgets.QLabel(tools.make_font(msg=translate['REBAR_RANGE'][self.lang] + ':'))
        self.rebarRangeBotLabel.setAlignment(QtCore.Qt.AlignRight)
        self.rebarRangeBotStr = QtWidgets.QLabel(tools.make_font(msg='0'))
        self.rebarRangeBotStr.setAlignment(QtCore.Qt.AlignLeft)

        self.currThickLabel = QtWidgets.QLabel(tools.make_font(msg=translate['CURR_THICK'][self.lang] + ':'))
        self.currThickLabel.setAlignment(QtCore.Qt.AlignRight)
        self.currThickStr = QtWidgets.QLabel(tools.make_font(msg='0'))
        self.currThickStr.setAlignment(QtCore.Qt.AlignLeft)

        self.realThickLabel = QtWidgets.QLabel(tools.make_font(msg=translate['REAL_THICK'][self.lang] + ':'))
        self.realThickLabel.setAlignment(QtCore.Qt.AlignRight)
        self.realThickStr = QtWidgets.QLabel(tools.make_font(msg='0'))
        self.realThickStr.setAlignment(QtCore.Qt.AlignLeft)

        self.rtVoltaLabel = QtWidgets.QLabel(tools.make_font(msg=translate['RT_VOLTA'][self.lang] + ':'))
        self.rtVoltaLabel.setAlignment(QtCore.Qt.AlignRight)
        self.rtVoltaStr = QtWidgets.QLabel(tools.make_font(msg='0'))
        self.rtVoltaStr.setAlignment(QtCore.Qt.AlignLeft)

        self.shiftLabel = QtWidgets.QLabel(tools.make_font(msg=translate['SHIFT'][self.lang] + ':'))
        self.shiftLabel.setAlignment(QtCore.Qt.AlignRight)
        self.shiftStr = QtWidgets.QLabel(tools.make_font(msg='0'))
        self.shiftStr.setAlignment(QtCore.Qt.AlignLeft)

        self.bottomLayout.addWidget(self.counterLabel, 0, 0, 1, 1)
        self.bottomLayout.addWidget(self.counterStr, 0, 1, 1, 1)
        self.bottomLayout.addWidget(self.rangeChosenLabel, 0, 3, 1, 1)
        self.bottomLayout.addWidget(self.rangeChosenStr, 0, 4, 1, 1)
        self.bottomLayout.addWidget(self.diameterLabel, 0, 6, 1, 1)
        self.bottomLayout.addWidget(self.diameterStr, 0, 7, 1, 1)
        self.bottomLayout.addWidget(self.rebarRangeBotLabel, 0, 9, 1, 1)
        self.bottomLayout.addWidget(self.rebarRangeBotStr, 0, 10, 1, 1)

        self.bottomLayout.addWidget(self.rtVoltaLabel, 1, 0, 1, 1)
        self.bottomLayout.addWidget(self.rtVoltaStr, 1, 1, 1, 1)
        self.bottomLayout.addWidget(self.shiftLabel, 1, 3, 1, 1)
        self.bottomLayout.addWidget(self.shiftStr, 1, 4, 1, 1)
        self.bottomLayout.addWidget(self.currThickLabel, 1, 6, 1, 1)
        self.bottomLayout.addWidget(self.currThickStr, 1, 7, 1, 1)
        self.bottomLayout.addWidget(self.realThickLabel, 1, 9, 1, 1)
        self.bottomLayout.addWidget(self.realThickStr, 1, 10, 1, 1)

        self.mainLayout.addWidget(infoGroupBox, 8, 0, 1, 5)

    def init_args(self):
        """
        初始化一些必要的参数，比如计数，计时器， 以及用于储存数据的队列。
        并检测设备连接状况， 检测校准文件。
        检测设备连接状况比较复杂， VNA必须要在另一个进程里进行，只能通过内置的本地server获取信息。
        :return:
        """
        logging.info("INIT ARGS....")
        self.count = 0  # To count valid data
        self.lastCount = 0  # To control bscan drawing
        self.vnaTimerCount = 0  # To check vna connexion
        self.rebarTimerCount = 0  # To check rebar connexion

        self.isMeasuring = False  # To mark current app collection status

        self.rebarConn = False  # To mark if rebar detector is connected
        self.vnaConn = False  # To mark if vna is connected

        self.voltageHist = []  # To record voltage history data, and show in graph
        self.rebarHist = []  # To record valid data comes from rebar detector
        self.currRadarData = []  # To record current data transformed by data from vna
        self.radarHist = []  # To record valid transformed data comes from vna

        self.F = self.config['SERIAL']['F']  # This value comes from VNA
        self.currRadarTimer = 0  # The interval of time while transforming VNA data

        self.rapidInterval = self.config['BASIC_PARA']['RAPID']

        self.f1Hz = eval(self.config['SERIAL']['F1_HZ'])  # 起始频率
        self.f2Hz = eval(self.config['SERIAL']['F2_HZ'])  # 终止频率

        self.load_calibration_action()

        logging.info("Start inner udp server to recv data...")
        self.innerUDPServer = UDPServer(self.config['UDP_SERVER'])
        if self.innerUDPServer.generate_server() == 0:
            self.innerUDPServer.start()
        if self.check_calibration_file() == 0:  # Check if calibration file exists
            logging.info("Start vna process to recv data...")
            self.T1 = self.config['SERIAL']['T1']
            self.T2 = eval(self.config['SERIAL']['T2'])
            self.sampRVNA = R60(use_center_and_span=self.config['SERIAL']['CENTER_SPAN'],
                                power_level_dbm=self.config['SERIAL']['POWER_LEVEL_DBM'],
                                f1_hz=self.f1Hz, f2_hz=self.f2Hz,
                                num_points=int(self.config['SERIAL']['FREQ_PTS']), parameter='s11',
                                formatD=self.config['SERIAL']['FORMATD'], caliFile=self.config['SERIAL']['CALI_FILE'],
                                freq=self.config['SERIAL']['TIMER_INTERVAL'],
                                bandwidth=self.config['SERIAL']['BANDWIDTH'])
            self.sampRVNA.start()
        else:
            tools.show_dialog(self, 'Inner server start failed! You can not use vna!', tools.ERROR)

        # logging.info("Start connect to Rebar device..")
        self.rebarClient = RebarConnector(self.config['REBAR_CONFIG'])

        self.drawWaveThread = WorkThread(freq=self.config['BASIC_PARA']['DRAW_WAVE_FREQ'])
        self.drawBSCANThread = WorkThread(freq=self.config['BASIC_PARA']['DRAW_BSCAN_FREQ'])
        self.drawVoltageThread = WorkThread(freq=self.config['BASIC_PARA']['DRAW_WAVE_FREQ'])

    def init_widget_status(self):
        """
        在等待收集设备连接情况的过程中，设置所有键位为不可点击状态
        :return:
        """
        self.setParaBtn.setEnabled(False)
        self.startBtn.setEnabled(False)
        self.stopBtn.setEnabled(False)
        self.initFreqEdit.setEnabled(False)
        self.endFreqEdit.setEnabled(False)
        self.freqPtsCombo.setEnabled(False)
        self.interFreqBWCombo.setEnabled(False)
        self.workModeCombo.setEnabled(False)
        self.rebarDiameterCombo.setEnabled(False)
        self.rebarRangeCombo.setEnabled(False)
        self.permittivityEdit.setEnabled(False)
        self.diameterMeasBtn.setEnabled(False)
        self.diameterGuessBtn.setEnabled(False)

    def init_slots(self):
        """
        初始化所有的槽函数， 按钮， 菜单， 线程等所连接的函数
        并启动收集设备连接情况的计时器
        :return:
        """
        logging.info("INIT CONNECT SLOTS....")
        self.aboutAction.triggered.connect(self.show_about_action)
        self.saveConfigAction.triggered.connect(self.save_config_action)
        self.loadConfigAction.triggered.connect(self.load_config_action)
        self.saveCurrentDataAction.triggered.connect(self.save_curr_data_action)
        self.reconRebarAction.triggered.connect(self.reconnect_rebar_action)
        self.reconVNAAction.triggered.connect(self.reconnect_vna_action)
        self.updateCaliFile.triggered.connect(self.update_cali_action)

        self.startBtn.clicked.connect(self.start_meas_action)
        self.stopBtn.clicked.connect(self.stop_meas_action)
        self.setParaBtn.clicked.connect(self.set_para_action)
        self.diameterMeasBtn.clicked.connect(self.diameter_meas_action)
        self.diameterGuessBtn.clicked.connect(self.diameter_guess_action)

        self.drawWaveThread.signal_updateUI.connect(self.draw_wave_action)
        self.drawBSCANThread.signal_updateUI.connect(self.draw_bscan_action)
        self.drawVoltageThread.signal_updateUI.connect(self.draw_voltage_action)

        self.innerUDPServer.trigger.connect(self.handle_radar_data_action)
        self.innerUDPServer.triggerF.connect(self.set_F_and_check_status_action)
        self.innerUDPServer.triggerExcept.connect(self.handle_vna_exception)
        self.rebarClient.trigger.connect(self.handle_rebar_data_action)
        self.rebarClient.triggerExcept.connect(self.handle_rebar_exception)

        # A timer to check VNA and Rebar Device Connection, 10s
        self.checkVNAConnectionTimer = QtCore.QTimer(self)
        self.checkVNAConnectionTimer.timeout.connect(self.check_VNA_conn_action)
        self.checkVNAConnectionTimer.start(int(1000))

        self.checkRebarConnectionTimer = QtCore.QTimer(self)
        self.checkRebarConnectionTimer.timeout.connect(self.check_rebar_conn_action)
        self.checkRebarConnectionTimer.start(int(1000))

    def set_F_and_check_status_action(self, F):
        """
        设置F参数， 并根据当前设备连接状态重置所有控件状态。
        如果F参数为空，则只重新设置控件状态。
        :param F:
        :return:
        """
        if len(F) != 0:
            logging.info("VNA is connected and F is set: " + str(F))
            self.F = F
            self.vnaConn = True

        self.diameterMeasBtn.setEnabled(False)
        self.diameterGuessBtn.setEnabled(False)
        if self.vnaConn and self.rebarConn:
            self.checkVNAConnectionTimer.stop()
            self.setParaBtn.setEnabled(True)
            self.startBtn.setEnabled(True)
            self.stopBtn.setEnabled(False)
            self.initFreqEdit.setEnabled(True)
            self.endFreqEdit.setEnabled(True)
            self.freqPtsCombo.setEnabled(True)
            self.interFreqBWCombo.setEnabled(True)
            self.workModeCombo.setEnabled(True)
            self.rebarDiameterCombo.setEnabled(True)
            self.rebarRangeCombo.setEnabled(True)
            self.permittivityEdit.setEnabled(True)
            self.set_rebar_para_action()
            self.statusBar.showMessage('VNA and Rebar is Connected.', 0)
        elif self.vnaConn:
            self.checkVNAConnectionTimer.stop()
            self.setParaBtn.setEnabled(True)
            self.startBtn.setEnabled(True)
            self.stopBtn.setEnabled(False)
            self.startBtn.setEnabled(True)
            self.initFreqEdit.setEnabled(True)
            self.endFreqEdit.setEnabled(True)
            self.freqPtsCombo.setEnabled(True)
            self.interFreqBWCombo.setEnabled(True)
            self.rebarDiameterCombo.setEnabled(False)
            self.rebarRangeCombo.setEnabled(False)
            self.permittivityEdit.setEnabled(False)
            self.statusBar.showMessage('VNA is Connected, Rebar is Disconnect.', 0)
        elif self.rebarConn:
            self.setParaBtn.setEnabled(True)
            self.stopBtn.setEnabled(False)
            self.startBtn.setEnabled(True)
            self.workModeCombo.setEnabled(True)
            self.rebarDiameterCombo.setEnabled(True)
            self.rebarRangeCombo.setEnabled(True)
            self.permittivityEdit.setEnabled(True)
            self.initFreqEdit.setEnabled(False)
            self.endFreqEdit.setEnabled(False)
            self.freqPtsCombo.setEnabled(False)
            self.interFreqBWCombo.setEnabled(False)
            self.set_rebar_para_action()
            self.statusBar.showMessage('Rebar is Connected, VNA is Disconnect.', 0)
        else:
            self.init_widget_status()
            self.statusBar.showMessage('Nothing is Connected, Please check device!', 0)

    def check_VNA_conn_action(self):
        """
        检测VNA连接状态计时器的槽函数， 8秒内如果无法连接到VNA则视为无法连接到VNA，
        关闭计时器并将VNA状态设置为FALSE
        :return:
        """
        if self.vnaTimerCount >= 8:
            self.checkVNAConnectionTimer.stop()
            self.vnaConn = False
        self.vnaTimerCount += 1
        logging.info("Try to connect to VNA...%d..." % self.vnaTimerCount)

    def check_rebar_conn_action(self):
        """
        检测钢筋测量仪连接状态计时器的槽函数，只连接一次，若无法连接到视为无法连接到设备。
        关闭计时器并将钢筋测量仪连接状态设为FALSE
        不论结果如何都会调用set_F_and_check_status_action函数重置控件状态
        :return:
        """
        logging.info("Check rebar connection...")
        if self.rebarClient.open_rebar() == 0:
            self.rebarConn = True
        else:
            self.rebarConn = False
        logging.info("Try to connect to Rebar Device...is Connected:" + str(self.rebarConn))
        self.set_F_and_check_status_action([])
        self.checkRebarConnectionTimer.stop()

    def handle_radar_data_action(self, s11):
        """
        内部用于接收VNA数据服务器的槽函数，从一开始初始化服务器时就启动，负责在用户点击开始之后解析收到的VNA数据，
        做逆傅里叶变换。
        在无法获取钢筋测量仪连接的情况下，所有接收并解析的VNA数据都会变成有效数据。

        :param s11: 服务器传回的VNA数据
        :return:
        """
        if self.isMeasuring:
            if self.vnaConn:
                s11 = np.array(s11[0::2]) + 1j * np.array(s11[1::2])
                [self.currRadarData, self.currRadarTimer] = invczt(s11, self.F, self.T1, self.T2,
                                                                   self.config['SERIAL']['BASE_POINTS'], 1, 0.5)
                self.currRadarData = self.currRadarData[40:-1]
                self.currRadarTimer = self.currRadarTimer[40:-1]
            if not self.rebarConn:
                self.radarHist.append(self.currRadarData)
                self.count += 1
                self.counterStr.setText(tools.make_font(msg=str(self.count)))

    def handle_vna_exception(self, errCode):
        """
        内部用于接收VNA数据异常情况的槽函数， 从一开始初始化，此线程就启动。 负责接收VNA的异常情况，有异常发生时
        会提醒用户，并设置VNA为不可链接状态，最后重置当前控件状态

        :param errCode: 错误码
        :return:
        """
        if errCode == -1:
            tools.show_dialog(self, 'Connect to VNA failed..', tools.ERROR)
            self.vnaConn = False
            self.set_F_and_check_status_action([])

    def handle_rebar_data_action(self, rebarData):
        """
        负责接收钢筋测量仪数据客户端线程的槽函数，在用户点击开始后，如果钢筋测量仪处于连接状态，此线程启动。
        连续接收来自钢筋测量仪发回的数据，解析数据并显示到界面。
        数据会经过和上一帧数据对比，如果位移有变化，则作为有效数据加入到内存变量中，如果没有变化，则只做显示。

        :param rebarData: 已经解析的钢筋测量仪数据列表：[量程，直径，电量，当前厚度，实际厚度，位移]
        :return:
        """
        if self.isMeasuring:
            self.currThickStr.setText(tools.make_font(msg=str(rebarData[3])))
            self.realThickStr.setText(tools.make_font(msg=str(rebarData[4])))
            self.shiftStr.setText(tools.make_font(msg=str(rebarData[5])))
            self.rtVoltaStr.setText(tools.make_font(msg=str(rebarData[2])))
            self.rebarRangeBotStr.setText(tools.make_font(msg=str(rebarData[0])))
            self.diameterStr.setText(tools.make_font(msg=str(rebarData[1])))
            if self.count == 0:
                self.count = self.count + 1
                self.counterStr.setText(tools.make_font(msg=str(self.count)))
                self.voltageHist.append(rebarData[2])
                self.rebarHist.append(rebarData)
                self.currRebarDist = self.rebarHist[-1][5]
                if self.vnaConn and len(self.currRadarData) > 0:
                    self.radarHist.append(self.currRadarData)
            else:
                # print("curr - front: " + str(self.currRebarDist) + '-' + str(rebarData[5]) + '=' + str(self.currRebarDist - rebarData[5]))
                if self.rapidInterval[0] <= self.currRebarDist - rebarData[5] <= self.rapidInterval[1] or \
                        -self.rapidInterval[1] <= self.currRebarDist - rebarData[5] <= -self.rapidInterval[0]:
                    self.count = self.count + 1
                    self.counterStr.setText(tools.make_font(msg=str(self.count)))
                    self.voltageHist.append(rebarData[2])
                    self.rebarHist.append(rebarData)
                    if self.vnaConn and len(self.currRadarData) > 0:
                        self.radarHist.append(self.currRadarData)
                elif self.currRebarDist - rebarData[5] > self.rapidInterval[1] or \
                        self.currRebarDist - rebarData[5] < -self.rapidInterval[1]:
                    self.statusBar.showMessage("滚太快了！ 慢一点！", 300)
                elif self.currRebarDist != rebarData[5] and \
                        -self.rapidInterval[0] < self.currRebarDist - rebarData[5] < self.rapidInterval[0]:
                    self.statusBar.showMessage("滚太慢了！ 快一点！", 300)
                self.currRebarDist = rebarData[5]

    def handle_rebar_exception(self, errCode):
        """
        负责处理钢筋测量仪异常情况的槽函数，根据错误码给与用户不同的提示，当然还不完善。。

        :param errCode: 错误码
        :return:
        """
        if errCode == errorhandle.DISCONNECT_ERROR or errCode == errorhandle.SEND_INSTRUCT_ERROR or \
                errCode == errorhandle.SERVER_CONNECT_EXCEPTION:
            if self.isMeasuring:
                msg = 'Lost connecxion of Rebar Detector!' + ' Stop collection automatically!'
            else:
                msg = 'Lost connecxion of Rebar Detector!'
            tools.show_dialog(self, msg, tools.ERROR)
            self.rebarConn = False
            self.set_F_and_check_status_action([])
            self.stop_meas_action()
        else:
            tools.show_dialog(self, 'Some thing is wrong while starting Rebar Detector! Error code:' + str(errCode),
                              tools.ERROR)

    def draw_wave_action(self):
        """
        画波形图的槽函数， 在VNA连接成功后，用户点击开始，线程开始根据最后一个有效VNA数据画图
        :return:
        """
        if self.isMeasuring and self.count > 1:
            self.currRadarTimer = self.currRadarTimer - self.currRadarTimer[self.config['BASIC_PARA']['ORIGIN']]
            distance = (self.currRadarTimer * 3 * 1e8) / (2 * math.sqrt(self.config['BASIC_PARA']['PERMITTIVITY']))
            self.wavePanel.handle_data(np.real(self.radarHist[-1]) * 10000, distance)

    def draw_bscan_action(self):
        """
        根据后100条VNA数据融合画雷达的灰度图。 用户点击开始，VNA处于连接状态下，开始画。
        由于不能画重复的数据，所以加了一个lastCount参数用于参考数据是否有更新。

        :return:
        """
        if self.isMeasuring and self.count > 1:
            if self.lastCount != self.count and len(self.radarHist) > self.lastCount:
                try:
                    self.bscanPanel.handle_data(np.real(self.radarHist[self.lastCount]) * 10000)
                    self.lastCount = self.count
                except Exception as e:
                    traceback.print_exc()
                    self.lastCount = self.count

    def draw_voltage_action(self):
        """
        画电量图的槽函数。在钢筋测量仪连接的状态下，根据最后200条有效数据的电量画图。

        :return:
        """
        if self.isMeasuring and self.count > 1:
            if len(self.voltageHist) < 200:
                self.voltagePanel.handle_data(self.voltageHist)
            else:
                tempVoltageHist = self.voltageHist[-200:-1]
                self.voltagePanel.handle_data(tempVoltageHist)

    def set_vna_para_action(self):
        """
        VNA参数设置更新
        :return:
        """
        try:
            currF1Hz = float(self.initFreqEdit.text()) * 1e6
            currF2Hz = float(self.endFreqEdit.text()) * 1e6
        except:
            tools.show_dialog(self, 'Parameters settings failed, please check and retry!', tools.ERROR)
            return
        self.sampRVNA.terminate()
        self.config['SERIAL']['F1_HZ'] = currF1Hz
        self.config['SERIAL']['F2_HZ'] = currF2Hz
        self.config['SERIAL']['BANDWIDTH'] = float(self.interFreqBWCombo.currentText())
        self.config['SERIAL']['FREQ_PTS'] = int(self.freqPtsCombo.currentText())
        self.sampRVNA = R60(use_center_and_span=self.config['SERIAL']['CENTER_SPAN'],
                            power_level_dbm=self.config['SERIAL']['POWER_LEVEL_DBM'],
                            f1_hz=self.f1Hz, f2_hz=self.f2Hz,
                            num_points=int(self.config['SERIAL']['FREQ_PTS']), parameter='s11',
                            formatD=self.config['SERIAL']['FORMATD'], caliFile=self.config['SERIAL']['CALI_FILE'],
                            freq=self.config['SERIAL']['TIMER_INTERVAL'],
                            bandwidth=self.config['SERIAL']['BANDWIDTH'])
        self.sampRVNA.start()
        logging.info("Set parameters of VNA Done....")
        self.statusBar.showMessage("Set parameters of VNA Done....", 3000)

    def set_rebar_para_action(self):
        """
        设置钢筋测量仪的参数，根据钢筋测量仪的需求，需要在设置参数后暂停一到两秒，等待配置完成
        :return:
        """
        try:
            self.config['BASIC_PARA']['REBAR_RANGE'] = self.rebarRangeCombo.currentText()
            self.config['BASIC_PARA']['REBAR_DIAMETER'] = int(self.rebarDiameterCombo.currentText())
            cmdRange = tools.parse_para_range(self.config['BASIC_PARA']['REBAR_RANGE'], self.lang)
            logging.info("Send para to rebar: Range/Diameter: " + str(cmdRange) + "/" + str(
                self.config['BASIC_PARA']['REBAR_DIAMETER']))

            if self.rebarClient.execute_cmd('range ' + str(self.rebarRangeCombo.currentIndex())) != 0:
                tools.show_dialog(self, 'Set parameter to rebar device exception!', tools.ERROR)
                return
            time.sleep(2)
            if self.rebarClient.execute_cmd('diameter ' + str(self.config['BASIC_PARA']['REBAR_DIAMETER'])) != 0:
                tools.show_dialog(self, 'Set parameter to rebar device exception!', tools.ERROR)
                return
            time.sleep(2)
            return 0
        except Exception as e:
            logging.error("Set rebar parameter exception..." + str(e))
            return -1

    def set_para_action(self):
        """
        参数设置函数， 在用户点击参数设置按钮后触发， 此函数会根据当前设备连接状态，自动发送当前的用户设置到设备。

        :return:
        """
        logging.info("Set parameters of APP....")
        if self.vnaConn:
            self.set_vna_para_action()
        else:
            tools.show_dialog(self, "Can not connect to VNA...", tools.WARNING)
            return
        if self.rebarConn:
            if self.set_rebar_para_action() != 0:
                tools.show_dialog(self, "Set rebar parameter failed!", tools.ERROR)
        else:
            tools.show_dialog(self, "Can not connect to Rebar Detector..", tools.WARNING)
            return
        tools.show_dialog(self, 'Set parameters done!', tools.INFO)

    def start_meas_action(self):
        """
        开始按钮的槽函数，在点击开始后，APP初始化所有需要用的参数，控件以及图像，根据当前的设备连接状态启动相应线程接收数据。
        将当前软件状态改为采集状态(isMeasuring = True)

        :return:
        """
        logging.info("Start measurement....")
        # Init last measuring data
        self.count = 0
        self.lastCount = 0
        self.voltageHist = []
        self.radarHist = []
        self.rebarHist = []
        self.currRadarData = []
        self.isMeasuring = True

        self.bscanPanel.init_img(self.dataAir)
        self.wavePanel.init_args(self.dataAir)
        self.voltagePanel.handle_data([])

        # Check permittivity
        try:
            permittivity = float(self.permittivityEdit.text())
            if permittivity <= 0 or permittivity > 15:
                raise ValueError('Permittivity out of range(1-15)')
            self.config['BASIC_PARA']['PERMITTIVITY'] = permittivity
        except:
            tools.show_dialog(self, 'Permittivity(1-15) settings exception!', tools.ERROR)
            return

        if self.vnaConn:
            self.drawWaveThread.start()
            self.drawBSCANThread.start()
            self.drawWaveThread.isexit = False
            self.drawBSCANThread.isexit = False

        if self.rebarConn:
            res = self.rebarClient.execute_cmd('start')
            if res != 0:  # 防止在失去REBAR连接以后程序出错
                tools.show_dialog(self, 'Start measurement failed..Error Code:' + str(res), tools.ERROR)
                self.handle_rebar_exception(res)
                return
            self.rebarClient.isRun = True
            self.rebarClient.start()
            self.drawVoltageThread.start()
            self.drawVoltageThread.isexit = False
            self.diameterMeasBtn.setEnabled(True)
            self.diameterGuessBtn.setEnabled(False)

        self.startBtn.setEnabled(False)
        self.stopBtn.setEnabled(True)
        self.setParaBtn.setEnabled(False)
        self.initFreqEdit.setEnabled(False)
        self.endFreqEdit.setEnabled(False)
        self.freqPtsCombo.setEnabled(False)
        self.interFreqBWCombo.setEnabled(False)
        self.workModeCombo.setEnabled(False)
        self.rebarDiameterCombo.setEnabled(False)
        self.rebarRangeCombo.setEnabled(False)
        self.permittivityEdit.setEnabled(False)
        self.menuFunc.setEnabled(False)
        self.menuConfiguration.setEnabled(False)
        self.counterStr.setText(tools.make_font(msg='0'))
        self.rtVoltaStr.setText(tools.make_font(msg='0'))
        self.realThickStr.setText(tools.make_font(msg='0'))
        self.currThickStr.setText(tools.make_font(msg='0'))
        self.shiftStr.setText(tools.make_font(msg='0'))

    def stop_meas_action(self):
        """
        停止按钮的槽函数。
        将采集状态改为未采集状态， 更新相应的控件状态， 停止采集以及画图线程。

        :return:
        """
        logging.info("Stop measurement...")
        self.isMeasuring = False

        if self.rebarConn:
            self.rebarClient.stop()
            self.drawVoltageThread.stop()
            self.rebarDiameterCombo.setEnabled(True)
            self.rebarRangeCombo.setEnabled(True)
            self.permittivityEdit.setEnabled(True)
        if self.vnaConn:
            self.drawWaveThread.stop()
            self.drawBSCANThread.stop()
            self.initFreqEdit.setEnabled(True)
            self.endFreqEdit.setEnabled(True)
            self.freqPtsCombo.setEnabled(True)
            self.interFreqBWCombo.setEnabled(True)

        self.setParaBtn.setEnabled(True)
        self.startBtn.setEnabled(True)
        self.stopBtn.setEnabled(False)
        self.diameterMeasBtn.setEnabled(False)
        self.diameterGuessBtn.setEnabled(False)
        self.workModeCombo.setEnabled(True)
        self.menuFunc.setEnabled(True)
        self.menuConfiguration.setEnabled(True)

    def diameter_meas_action(self):
        """
        直径测量按钮的槽函数，在钢筋测量仪处于连接状态并处于采集状态中，可以点击此按钮。
        此函数会暂停钢筋测量仪工作状态，并发送直径检测指令，进入直径检测模式。
        :return:
        """
        logging.info("Start diameter MEASURE action...")
        if self.rebarConn:
            if self.isMeasuring:
                cmdRes = self.rebarClient.execute_cmd('stop')
                if cmdRes != 0:
                    tools.show_dialog(self, 'Send command to device failed, error code:' + str(cmdRes), tools.ERROR)
                    return
                time.sleep(2)
                cmdRes = self.rebarClient.execute_cmd('measure')
                if cmdRes != 0:
                    tools.show_dialog(self, 'Send command to device failed, error code:' + str(cmdRes), tools.ERROR)
                    return
                time.sleep(2)
                self.diameterGuessBtn.setEnabled(True)
            else:
                tools.show_dialog(self, 'You need to start measurement firstly!', tools.WARNING)
        else:
            tools.show_dialog(self, 'Rebar device is not connected!', tools.ERROR)

    def diameter_guess_action(self):
        logging.info("Start diameter GUESS action...")
        if self.rebarConn:
            cmdRes = self.rebarClient.execute_cmd('guess')
            if cmdRes != 0:
                tools.show_dialog(self, 'Send command to device failed, error code:' + str(cmdRes), tools.ERROR)
                return
            time.sleep(2)
        else:
            tools.show_dialog(self, 'Rebar device is not connected!', tools.ERROR)

    def reconnect_rebar_action(self):
        """
        重连钢筋测量仪选项的槽函数，可以尝试重新连接钢筋测量仪并根据连接情况更新控件状态

        :return:
        """
        if self.rebarConn:
            self.rebarClient.disconnect()
        if self.rebarClient.open_rebar() == 0:
            self.rebarConn = True
            tools.show_dialog(self, 'Rebar detector is connected!', tools.INFO)
        else:
            self.rebarConn = False
            tools.show_dialog(self, 'Still can not connect to rebar detector', tools.INFO)
        logging.info("Try to reconnect to Rebar Device...is Connected:" + str(self.rebarConn))
        self.set_F_and_check_status_action([])

    def reconnect_vna_action(self):
        """
        重连VNA，如果内部服务器未启动则先启动内部服务器，并根据当前配置重新启动VNA进程。
        更具当前设备的连接状态更新控件情况
        :return:
        """
        if not self.innerUDPServer.is_running:
            self.innerUDPServer.stop()
            if self.innerUDPServer.generate_server() != 0:
                tools.show_dialog(self, "Start inner server failed..Maybe you can reopen app..", tools.ERROR)
                return
        self.innerUDPServer.start()
        if self.check_calibration_file() == 0:  # Check if calibration file exists
            logging.info("Start vna process to recv data...")
            self.T1 = self.config['SERIAL']['T1']
            self.T2 = eval(self.config['SERIAL']['T2'])
            self.sampRVNA = R60(use_center_and_span=self.config['SERIAL']['CENTER_SPAN'],
                                power_level_dbm=self.config['SERIAL']['POWER_LEVEL_DBM'],
                                f1_hz=self.f1Hz, f2_hz=self.f2Hz,
                                num_points=int(self.config['SERIAL']['FREQ_PTS']), parameter='s11',
                                formatD=self.config['SERIAL']['FORMATD'],
                                caliFile=self.config['SERIAL']['CALI_FILE'],
                                freq=self.config['SERIAL']['TIMER_INTERVAL'],
                                bandwidth=self.config['SERIAL']['BANDWIDTH'])
            self.sampRVNA.start()
            self.checkVNAConnectionTimer.start()
        else:
            tools.show_dialog(self, 'Inner server start failed! You can not use vna!', tools.ERROR)

    def update_cali_action(self):
        """
        重置原点校准文件步骤
        :return:
        """
        logging.info("Update Calibration file action is invoked...")
        logging.info("Origin point now is : " + str(self.config['BASIC_PARA']['ORIGIN']))
        if self.rebarConn:
            self.currRebarConn = True
        else:
            self.currRebarConn = False

        if not self.vnaConn:
            tools.show_dialog(self, '雷达无法连接， 无法校准！', tools.ERROR)
            return
        self.caliCountTimer = QtCore.QTimer(self)
        self.caliCountTimer.timeout.connect(self.cali_count_action)
        res = tools.show_dialog(self, '校准即将开始，请对金属使用工具，滑动轮子采集数据', tools.INFO)
        if res == -1:
            return
        self.rebarConn = False
        self.startBtn.click()
        self.caliCountTimer.start(10)
        self.caliTime = 0

    def cali_count_action(self):
        """
        校准时需要采集100条数据，分别采集对空数据以及对金属的数据，将结果写入当前配置，并将数据保存到
        resources/calibration.pkl以便下次使用
        :return:
        """
        if self.count >= 100:
            self.stopBtn.click()
            if self.caliTime == 0:
                self.dataMetal = np.array(self.radarHist, dtype=complex)
                res = tools.show_dialog(self, '对金属校准完毕，请空使用仪器，滑动轮子采集数据', tools.INFO)
                if res == -1:
                    self.caliCountTimer.stop()
                    self.stopBtn.click()
                    if self.currRebarConn:
                        self.rebarConn = True
                    return
                self.startBtn.click()
                self.caliTime = 1
        if self.caliTime == 1 and self.count >= 100:
            self.stopBtn.click()
            self.dataAir = np.array(self.radarHist, dtype=complex)
            self.caliCountTimer.stop()
            self.config['BASIC_PARA']['ORIGIN'] = remove_back(self.dataMetal, self.dataAir)
            logging.info("Update Calibration of origin point to: " + str(self.config['BASIC_PARA']['ORIGIN']))
            self.save_cali_data_action(self.dataAir, self.dataMetal)
            tools.show_dialog(self, "更新校准文件完毕!", tools.INFO)
            if self.currRebarConn:
                self.rebarConn = True

    def show_about_action(self):
        aboutActionDialog = AboutDialog(self.lang)
        aboutActionDialog.exec_()

    def save_cali_data_action(self, dataAir, dataMetal):
        """
        保存更新的校准文件到./resources/calibration.pkl
        :param dataAir: 对空数据
        :param dataMetal: 对金属数据
        :return:
        """
        if len(dataAir) > 99 and len(dataMetal) > 99:
            res = save_data_pickle([dataAir, dataMetal], filepath='./resources', instType='cali',
                                   filetype='pickle', filename='calibration')
            if res == errorhandle.SAVE_PICKLE_ERROR:
                tools.show_dialog(self, 'Save calibration file exception, error code:' + str(res), tools.ERROR)
                return
            logging.info("Save current calibration file to " + str(res))
        else:
            tools.show_dialog(self, 'Calibration data is erupted...', tools.ERROR)

    def save_curr_data_action(self):
        logging.info('Save data action is invoked...File type: ' + self.config['APP']['SAVE_TYPE'])
        if len(self.rebarHist) > 0:
            res = save_data_pickle(self.rebarHist, instType='rebar', filetype=self.config['APP']['SAVE_TYPE'])
            if res == errorhandle.SAVE_PICKLE_ERROR:
                tools.show_dialog(self, 'Save file exception, error code:' + str(res), tools.ERROR)
                return
            logging.info('Save rebar file done at: ' + str(res))
        if len(self.radarHist) > 0:
            res = save_data_pickle(self.radarHist, instType='vna', filetype=self.config['APP']['SAVE_TYPE'])
            if res == errorhandle.SAVE_PICKLE_ERROR:
                tools.show_dialog(self, 'Save file exception, error code:' + str(res), tools.ERROR)
                return
            tools.show_dialog(self, 'Save files successfully, find them at /data!', tools.INFO)
            logging.info('Save radar file done at: ' + str(res))
        else:
            tools.show_dialog(self, 'Current rebar data or radar data is empty!', tools.WARNING)

    def save_config_action(self):
        config_file_name, ok = QFileDialog.getSaveFileName(None, "文件保存到", tools.CONFIG_FILE)
        if ok:
            logging.info("Save current configuration to: " + config_file_name)
            if config_file_name[-5:] != '.yaml':
                config_file_name = config_file_name + '.yaml'
            res = tools.generate_yaml_doc(config_file_name, self.config)
            if type(res) != 'int':
                tools.show_dialog(self.ui_main.mainwindow, "保存成功：" + res, tools.INFO)
            else:
                tools.show_dialog(self.ui_main.mainwindow, "保存失败：" + str(res), tools.ERROR)
            logging.info("Save configuration result: " + str(res))
        else:
            logging.info("User canceled save configuration action.")

    def load_config_action(self):
        config_file_name, ok = QFileDialog.getOpenFileName(None, "选择文件", tools.CONFIG_FILE,
                                                           "yaml file(*.yaml);;yml file(*.yml)")
        if ok:
            logging.info("User select load configuration: " + config_file_name)
            self.config = tools.load_yaml(config_file_name)
        else:
            logging.info("User canceled load configuration action.")

    def load_calibration_action(self):
        """
        在渲染界面前，加载calibration校准文件，计算原点， 如果校准文件的数据和当前配置不一致则提醒用户更新校准文件。
        :return:
        """
        logging.info("Load calibration action is invoked...")
        import pickle
        try:
            with open(self.config['APP']['ORIGIN_CALI_FILE'], 'rb') as f:
                self.dataAir, self.dataMetal = pickle.load(f)
                self.config['BASIC_PARA']['ORIGIN'] = remove_back(self.dataMetal, self.dataAir)
                logging.info("Load calibration as : " + str(self.config['BASIC_PARA']['ORIGIN']))
                if self.dataAir.shape[1] != self.config['SERIAL']['SAMPLE_POINTS']:
                    tools.show_dialog(self, "校准数据格式与当前设置不同，请重新校准!", tools.WARNING)
                    return
        except Exception as e:
            traceback.print_exc()
            logging.error('Load calibration file failed, set to default 23!')
            self.config['BASIC_PARA']['ORIGIN'] = 23

    def check_calibration_file(self):
        logging.info("Check calibration file...")
        if self.config['SERIAL']['CALI_FILE']:
            import os
            if os.path.exists(self.config['SERIAL']['CALI_FILE']):
                return 0
        tools.show_dialog(self, 'Can not find calibration file! VNA connector will be closed.', tools.WARNING)
        logging.info("Check calibration file failed...")
        return errorhandle.VNA_UNFOUND_CALI_EXCEPTION

    def closeEvent(self, a0: QtGui.QCloseEvent):
        """
        The action should be done before close the program.
        """
        if self.vnaConn:
            self.innerUDPServer.stop()
            self.innerUDPServer.terminate()
            self.sampRVNA.terminate()
        if self.rebarConn:
            if self.isMeasuring:
                self.rebarClient.stop()
            if self.rebarClient.isConnected:
                self.rebarClient.disconnect()


if __name__ == '__main__':
    basic_log_config()
    app = QtWidgets.QApplication(sys.argv)
    app_name = load_yaml(tools.CONFIG_FILE)["APP"]["APP_NAME"].replace('_', ' ')
    try:
        socket = QLocalSocket()
        socket.connectToServer(app_name)
        if socket.waitForConnected(500):
            tools.message_box("错误", "该程序正在运行中！")
            app.quit()
        else:
            """
            以单例模式启动
            """
            local_server = QLocalServer()
            local_server.listen(app_name)
            main_window = MainFrame()
            main_window.show()
            sys.exit(app.exec_())
    except Exception as e:
        traceback.print_exc()
        tools.message_box("错误", "启动失败： " + str(e))
